11장. 바뀌면 다시 맞추기
원문 11장 "결합 재조정"을 입문자용으로 다시 풀었다.
핵심은 "처음부터 완벽한 설계"가 아니라 "변화가 오면 균형을 다시 맞추는 습관"이다.
10장에서 우리는 결합 균형을 배웠다.
- 강한 연결은 가까이에 둔다.
- 먼 연결은 약하게 만든다.
- 자주 바뀌는 지식은 흩어지지 않게 한다.
그런데 현실의 소프트웨어는 멈춰 있지 않다.
처음에는 균형이 맞았던 구조도 시간이 지나면 어긋난다.
11장의 핵심은 이것이다.
설계는 한 번 정하고 끝나는 것이 아니라, 변화에 맞춰 계속 재조정하는 일이다.
0. 이 장의 새 단어
| 단어 | 아주 쉬운 뜻 |
|---|---|
| 재조정 | 깨진 균형을 다시 맞추는 일 |
| 전술 변경 | 지금 당장 필요한 작은 수정 |
| 전략 변경 | 시스템의 방향과 구조를 바꾸는 큰 변화 |
| 추출 | 흩어진 지식을 별도 함수, 클래스, 모듈로 빼내는 것 |
| 이동 | 책임에 더 가까운 곳으로 코드를 옮기는 것 |
| 계약 결합 | 내부 구조 대신 약속된 입력과 출력으로 연결되는 결합 |
1. 귀납 도입: 처음엔 괜찮았는데 왜 나중엔 나빠질까?
처음에는 할인 로직이 작았다.
static dynamic DiscountFor(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
if (order.Total >= 50000) // 조건이 참일 때만 아래 결정을 실행합니다.
{
return 3000; // 결과를 호출자에게 돌려줍니다.
}
return 0; // 결과를 호출자에게 돌려줍니다.
}
이 정도면 굳이 큰 설계가 필요 없다.
가깝고, 단순하고, 자주 바뀌지 않는다면 좋은 선택이다.
그런데 몇 달 뒤 요구사항이 늘어난다.
- 신규 가입 할인
- 등급별 할인
- 카테고리별 할인
- 쿠폰 중복 제한
- 특정 기간 이벤트
- 관리자 미리보기
처음 코드는 나쁜 코드가 아니었다.
다만 세상이 바뀌었다.
변동성이 높아졌고, 로직이 여러 곳으로 퍼졌고, 내부 규칙을 알아야 하는 코드가 늘었다.
이때 필요한 것은 "처음 설계가 틀렸다"는 비난이 아니다.
"지금 균형이 바뀌었으니 다시 맞추자"는 판단이다.
2. 이 장에서 딱 5가지만
- 설계는 고정된 답이 아니라 계속 움직이는 균형이다.
- 작은 전술 변경은 구조를 크게 흔들지 않을 수 있다.
- 전략 변경은 결합의 세기, 거리, 변동성을 다시 바꾼다.
- 강한 결합이 멀리 퍼지면 추출하고 이동한다.
- 재조정은 너무 늦어도, 너무 크게 한 번에 해도 위험하다.
3. 전술 변경과 전략 변경
변경에는 크기가 있다.
전술 변경은 눈앞의 작은 수정이다.
- 문구 변경
- 계산식 한 줄 수정
- 버그 수정
- 작은 필드 추가
전술 변경은 기존 구조 안에서 처리할 수 있다.
반면 전략 변경은 시스템의 방향을 바꾼다.
- 새로운 사업 모델 도입
- 주요 기능의 책임 이동
- 팀 구조 변경
- 외부 API 교체
- 대량 트래픽 대응
- 도메인 규칙의 빈번한 변경
전략 변경은 기존 균형을 흔든다.
예를 들어 "쿠폰은 거의 안 바뀐다"고 생각해서 간단히 만들었는데, 이제 쿠폰이 매주 마케팅의 핵심 수단이 되었다면?
변동성이 낮음에서 높음으로 바뀐 것이다.
이때는 "그냥 if 하나 더 추가"가 계속 쌓이면 위험하다.
4. 전략 변경이 균형을 깨는 네 가지 길
원문은 여러 변화 원인을 다룬다. 입문자에게는 네 칸으로 보면 쉽다.
| 변화 | 무엇이 달라지나 | 예 |
|---|---|---|
| 기능 요구사항 | 시스템이 해야 할 일이 달라짐 | 예약 기능 추가 |
| 비즈니스 전략 | 중요한 규칙의 우선순위가 달라짐 | 쿠폰 중심 마케팅 |
| 조직 변화 | 책임을 가진 팀이 달라짐 | 결제팀과 주문팀 분리 |
| 환경 변화 | 외부 조건이 달라짐 | 새 결제 API 도입 |
이 변화들은 결합의 세 차원을 바꾼다.
기능이 늘면 세기가 강해질 수 있다.
팀이 나뉘면 거리가 멀어질 수 있다.
비즈니스가 바뀌면 변동성이 높아질 수 있다.
그래서 전략 변경 뒤에는 질문해야 한다.
예전에는 균형이 맞았던 연결이 지금도 맞는가?
5. 재조정 1: 강한 결합이 생기면 거리를 줄인다
어떤 코드가 서로를 자세히 알아야 한다면, 그 둘은 가까이 두는 편이 낫다.
예를 들어 주문 총액 계산이 여러 화면에 흩어져 있다고 하자.
static dynamic CheckoutTotal(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return order.Price - order.Discount + order.ShippingFee; // 결과를 호출자에게 돌려줍니다.
}
static dynamic AdminPreview(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return order.Price - order.Discount + order.ShippingFee; // 결과를 호출자에게 돌려줍니다.
}
두 함수는 주문의 내부 계산 규칙을 강하게 알고 있다.
그런데 다른 곳에 흩어져 있다.
재조정 방향은 추출과 이동이다.
public class Order // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public dynamic Total() // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return this.Price - this.Discount + this.ShippingFee; // 결과를 호출자에게 돌려줍니다.
}
}
이제 화면은 묻기만 한다.
var total = order.Total(); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
강한 지식을 책임에 가까운 곳으로 옮긴 것이다.
6. 재조정 2: 변동성이 높아지면 세기나 거리를 낮춘다
자주 바뀌는 것은 조심해야 한다.
만약 바뀌는 지식이 여러 곳에 강하게 묶여 있다면 두 가지 중 하나를 해야 한다.
- 세기를 낮춘다.
- 거리를 줄인다.
세기를 낮춘다는 것은 내부 규칙 대신 안정적인 계약을 쓰는 것이다.
var price = pricing_policy.FinalPrice(order); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
이 코드를 쓰는 쪽은 할인 내부 규칙을 모른다.
정책 객체가 알아서 계산한다.
거리를 줄인다는 것은 자주 함께 바뀌는 코드를 같은 책임 안으로 모으는 것이다.
pricing/
policy.py
coupon.py
campaign.py
변동성이 높아졌다면 그냥 참지 말고 구조를 다시 보아야 한다.
7. 재조정 3: 거리가 멀어지면 경계를 강화한다
처음에는 한 팀이 관리하던 코드가 나중에 두 팀으로 나뉠 수 있다.
처음에는 한 애플리케이션이던 시스템이 나중에 여러 서비스가 될 수 있다.
이때 거리가 멀어진다.
거리가 멀어지면 내부 구조를 직접 공유하는 방식은 위험해진다.
나쁜 방향:
payment.Charge(order.Customer.Card.RawNumber, order.Items[0].SellerId); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
결제 쪽이 주문과 고객의 내부 구조를 너무 많이 안다.
나은 방향:
payment.Charge(PaymentRequest.FromOrder(order)); // 이 호출은 두 부품 사이의 연결을 보여 줍니다.
더 멀어진 시스템에서는 명확한 요청 모델이나 이벤트 계약을 둔다.
{
"orderId": "A-100",
"amount": 32000,
"currency": "KRW"
}
거리가 멀어질수록 경계는 더 분명해야 한다.
8. 재조정은 복잡함을 없애는 것이 아니라 옮기기도 한다
리팩토링을 하면 모든 복잡함이 사라질 것처럼 느껴질 수 있다.
하지만 실제로는 복잡함이 옮겨지는 경우가 많다.
예를 들어 여러 곳에 있던 할인 규칙을 PricingPolicy로 모았다고 하자.
장점:
- 중복이 줄었다.
- 변경 지점이 하나로 모였다.
- 멀리 있는 코드가 세부 규칙을 몰라도 된다.
대신 새 비용도 생긴다.
PricingPolicy를 이해해야 한다.- 정책 객체의 입력과 출력을 맞춰야 한다.
- 테스트를 새로 정리해야 한다.
재조정의 목표는 복잡함을 마법처럼 없애는 것이 아니다.
복잡함을 더 적절한 장소에 두는 것이다.
9. 안티패턴 1: 한 번에 전부 갈아엎기
균형이 깨졌다고 느끼면 전체를 새로 만들고 싶어진다.
"이번 기회에 아키텍처를 다 바꾸자."
"모든 모듈을 새 구조로 옮기자."
"기존 코드는 전부 틀렸다."
이런 큰 폭 변경은 위험하다.
- 변경 범위가 커진다.
- 검증이 어려워진다.
- 중간 상태가 오래 지속된다.
- 실제 문제가 아닌 곳까지 바꾸게 된다.
재조정은 가능한 한 작은 단위로 하는 편이 좋다.
오늘 가장 자주 바뀌는 정책 하나를 모은다.
이번 배포에서 가장 멀리 새는 내부 구조 하나를 계약으로 바꾼다.
그 다음 효과를 본다.
작은 재조정이 쌓이면 큰 구조가 바뀐다.
10. 안티패턴 2: 계속 미루기
반대쪽 위험도 있다.
"지금은 바빠서."
"나중에 한 번에 정리하자."
"이번만 if 하나 추가하자."
이 말이 한두 번이면 괜찮다.
하지만 같은 방향의 변경이 반복되면 균형이 이미 깨졌다는 신호다.
특히 다음 신호를 조심하자.
- 같은 규칙을 세 곳 이상 고친다.
- 한 변경이 여러 팀의 확인을 필요로 한다.
- 테스트보다 배포 순서 조율이 더 어렵다.
- 작은 요구사항인데 수정 파일이 계속 많다.
- 특정 모듈을 고치면 예상 밖의 화면이 깨진다.
이런 신호가 반복되면 재조정을 미룰수록 비용이 커진다.
11. 재조정 판단 순서
실무에서는 다음 순서로 생각하면 좋다.
- 이번 변경은 전술 변경인가, 전략 변경인가?
- 바뀐 것은 세기인가, 거리인가, 변동성인가?
- 강한 지식이 멀리 퍼졌는가?
- 자주 바뀌는 지식이 여러 곳에 흩어졌는가?
- 내부 구조 대신 계약으로 바꿀 수 있는가?
- 책임에 더 가까운 곳으로 옮길 수 있는가?
- 한 번에 바꾸지 않고 작은 단계로 나눌 수 있는가?
이 순서는 완벽한 답을 주지는 않는다.
하지만 "무엇을 리팩토링해야 하는가"를 더 구체적으로 만든다.
12. 작은 예제: 관리자 미리보기
처음에는 주문 화면만 가격을 계산했다.
static dynamic Checkout(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
var total = order.Price - order.Discount; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
return total; // 결과를 호출자에게 돌려줍니다.
}
나중에 관리자 화면도 같은 계산을 미리 보여줘야 한다.
static dynamic AdminPreview(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
var total = order.Price - order.Discount; // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
return total; // 결과를 호출자에게 돌려줍니다.
}
여기까지는 중복이 작다.
하지만 할인 정책이 자주 바뀌기 시작하면 위험하다.
재조정:
public class PricingService // 이 타입은 예제에서 책임과 결합의 경계를 보여 줍니다.
{
public dynamic TotalFor(dynamic order) // 이 메서드의 입력과 출력이 공개 약속입니다.
{
return order.Price - order.Discount; // 결과를 호출자에게 돌려줍니다.
}
}
화면들은 같은 서비스를 쓴다.
var total = pricing_service.TotalFor(order); // 이 값이나 객체를 알게 되는 순간 결합 지점이 됩니다.
이 리팩토링의 이유는 "서비스라는 이름이 멋져서"가 아니다.
가격 규칙의 변동성이 높아지고, 규칙 지식이 여러 화면에 퍼졌기 때문이다.
13. 재조정에도 타이밍이 있다
너무 이른 재조정은 과설계가 된다.
아직 바뀔지 모르는 것을 미리 복잡하게 나누면 코드가 어려워진다.
변동성이 실제로 낮다면 단순한 구조가 더 낫다.
너무 늦은 재조정은 빚이 된다.
같은 변경이 반복되고, 여러 곳이 계속 흔들리는데도 방치하면 나중에 고치기 어렵다.
좋은 타이밍은 대개 반복에서 보인다.
- 처음 한 번은 단순하게 둔다.
- 두 번째에는 주의 깊게 본다.
- 세 번째에는 패턴이라고 의심한다.
반복되는 변경은 설계가 보내는 신호다.
14. 정리
11장은 설계를 살아 있는 것으로 보게 만든다.
좋은 설계는 처음부터 모든 것을 예측하는 설계가 아니다.
변화가 왔을 때 무엇이 깨졌는지 보고, 필요한 만큼 다시 맞추는 설계다.
재조정의 기본 방향은 세 가지다.
- 강한 결합은 가까운 곳으로 옮긴다.
- 먼 결합은 안정적인 계약으로 약하게 만든다.
- 자주 바뀌는 지식은 흩어지지 않게 모은다.
그리고 두 가지를 조심한다.
- 한 번에 전부 갈아엎지 않는다.
- 반복되는 불균형을 계속 미루지 않는다.
설계는 완성품이 아니라 조율 과정이다.
더 해보기
- 최근 반복해서 고친 규칙 하나를 떠올려보자.
- 그 규칙은 어디에 흩어져 있는가?
- 추출해야 하는가, 이동해야 하는가, 계약을 세워야 하는가?
- 한 번에 다 바꾸지 않고 첫 단계 하나만 정해보자.
연습문제
- 설명.
바뀌면 다시 맞추기의 핵심을 처음 듣는 사람에게 한 문장으로 설명하라. - 구분. 두 개념(
재조정,대규모 재작성)을 실제 예시 하나로 구분하라. - 적용. 내 프로젝트나 학습 노트에서 이 장의 개념을 적용해 작게 개선할 지점을 하나 고르라.
부록 A. 쉬운 용어 사전
| 한글 용어 | 원문 영문명 | 아주 쉬운 뜻 | 이 장에서 나온 위치 |
|---|---|---|---|
| 재조정 | Recoupling | 변경 패턴이 달라졌을 때 결합 구조를 다시 맞추는 작업. | 부록 B와 본문 예시 |
| 대규모 재작성 | - | 기존 구조를 크게 갈아엎는 위험한 변경 방식. | 부록 B와 본문 예시 |
| 지금 고치기 | - | 현재 비용이 이미 커졌다고 보고 바로 구조를 조정하는 선택. | 부록 B와 본문 예시 |
| 나중에 고치기 | - | 아직 비용보다 변경 위험이 더 크다고 보고 미루는 선택. | 부록 B와 본문 예시 |
부록 B. 헷갈리는 개념 비교표
| A | B | 구분 포인트 |
|---|---|---|
| 재조정 | 대규모 재작성 | 재조정은 필요한 만큼 맞추고, 재작성은 한 번에 갈아엎는 위험한 선택이다. |
| 지금 고치기 | 나중에 고치기 | 변경 비용과 타이밍을 보고 결정해야 한다. |
부록 C. 더 읽을 자료
- 이 장의
더 해보기섹션 — 이미 모아 둔 공식 문서나 실습 링크가 있으면 여기서 먼저 확인한다. - 같은 책의
0장 한눈에 보기— 용어가 막히면 0장의 용어집과 개념 척추로 돌아간다. - 원본 딥다이브판 같은 장 — 입문판을 읽고 큰 흐름이 잡힌 뒤 세부 논리를 더 깊게 확인한다.
- 이 장의
flashcards.json— 읽은 직후 질문만 보고 답을 떠올리는 회상 연습에 쓴다.
부록 D. 연습문제 풀이
- 설명 예시.
바뀌면 다시 맞추기는 변경이 어디로 번지는지 보고, 필요한 연결과 줄여야 할 연결을 구분하게 해 주는 장이다. 중요한 것은 용어를 외우는 것이 아니라, 이 개념이 어떤 입력·부품·결정에 영향을 주는지 말로 풀어 보는 것이다. - 구분 예시. 두 개념(
재조정,대규모 재작성)의 차이는 이렇게 잡으면 된다. 재조정은 필요한 만큼 맞추고, 재작성은 한 번에 갈아엎는 위험한 선택이다. 실제 사례를 볼 때는 목적, 입력, 실패했을 때의 증상을 따로 적어 보면 헷갈리지 않는다. - 적용 예시. 가장 작은 개선부터 고른다. 예를 들어 이름을 더 분명히 하거나, 평가 기준을 한 줄 추가하거나, 직접 알 필요 없는 내부 정보를 감추는 식으로 시작한다. 한 번에 크게 갈아엎는 것보다 작은 변경 하나를 확인하며 진행하는 쪽이 입문 단계에 맞다.
클릭하거나 Space를 눌러 뒤집기